//
// Created by QU on 24-6-20.
//

#include <iostream>

///
///     auto的推导规则.
///
// 1．如果auto声明的变量是按值初始化，则推导出的类型会忽略cv限定符。进一步解释为，在使用auto声明变量时，既没有使用引
// 用，也没有使用指针，那么编译器在推导的时候会忽略const和volatile限定符。当然auto本身也支持添加cv限定符：
// const int i = 5;
// auto j = i;        // auto推导类型为int，而非const int
// auto &m = i;       // auto推导类型为const int，m推导类型为const int&
// auto *k = i;       // auto推导类型为const int，k推导类型为const int*
// const auto n = j;  // auto推导类型为int，n的类型为const in

// 2．使用auto声明变量初始化时，目标对象如果是引用，则引用属性会被忽略：
// int i = 5;
// int &j = i;
// auto m = j;    // auto推导类型为int，而非int&


// 3．使用auto和万能引用声明变量时（见第6章），对于左值会将auto推导为引用类型：
// int i = 5;
// auto&& m = i;    // auto推导类型为int& （这里涉及引用折叠的概念）
// auto&& j = 5;    // auto推导类型为int

// 4．使用auto声明变量，如果目标对象是一个数组或者函数，则auto会被推导为对应的指针类型：
// int i[5];
// auto m = i;    // auto推导类型为int*
// int sum(int a1, int a2)
// {
    // return a1+a2;
// }
// auto j = sum   // auto推导类型为int (__cdecl *)(int,int)


// 5．当auto关键字与列表初始化组合时，这里的规则有新老两个版本，这里只介绍新规则（C++17标准）。
// （1）直接使用列表初始化，列表中必须为单元素，否则无法编译，auto类型被推导为单元素的类型。
// （2）用等号加列表初始化，列表中可以包含单个或者多个元素， auto类型被推导为std::initializer_list<T>，其中T是元素类
// 型。请注意，在列表中包含多个元素的时候，元素的类型必须相同，否则编译器会报错。
// auto x1 = { 1, 2 };      // x1类型为 std::initializer_list<int>
// auto x2 = { 1, 2.0 };    // 编译失败，花括号中元素类型不同
// auto x3{ 1, 2 };         // 编译失败，不是单个元素
// auto x4 = { 3 };         // x4类型为std::initializer_list<int>
// auto x5{ 3 };            // x5类型为int


/*
class Base {
public:
    virtual void f()
    {
        std::cout << "Base::f()" << std::endl;
    };
};
class Derived : public Base {
public:
    virtual void f() override
    {
        std::cout << "Derived::f()" << std::endl;
    };
};
Base* d = new Derived();
auto b = *d;
b.f()

*/

// lambda表达式中使用auto类型推导
// auto l = [](int &i)->auto& { return i; };
// auto x1 = 5;
// auto &x2 = l(x1);
// assert(&x1 == &x2);    // 有相同的内存地址






/////////////////////////////////////////////////////////////////////////////
///     decltype

/*
template<class T>
auto return_ref(T& t)
{
    return t;
}
int x1 = 0;
static_assert(
    std::is_reference_v<decltype(return_ref(x1))>// 编译错误，返回值 不为引用类型
    );
*/

// 在上面的代码中，我们期望return_ref返回的是一个T的引用类型，但是如果编译此段代码，则必然会编译失败，因为auto被推导
// 为值类型，这就是第3章所讲的auto推导规则2。如果想正确地返回引用类型，则需要用到decltype说明符，例如：

template<class T>
auto return_ref(T& t)->decltype(t)
{
    return t;
}
int x1 = 0;
static_assert(
    std::is_reference_v<decltype(return_ref(x1))>    // 编译成功
    );
// 以上两段代码几乎相同，只是在return_ref函数的尾部用
// decltype(t)声明了返回类型，但是代码却可以顺利地通过编译。

// decltype(e)（其中e的类型为T）的推导规则有5条。
// 1．如果e是一个未加括号的标识符表达式（结构化绑定除外）或 者未加括号的类成员访问，则decltype(e)推断出的类型是e的类型
// T。如果并不存在这样的类型，或者e是一组重载函数，则无法进行推导。
// 2．如果e是一个函数调用或者仿函数调用，那么decltype(e) 推断出的类型是其返回值的类型。
// 3．如果e是一个类型为T的左值，则decltype(e)是T&。
// 4．如果e是一个类型为T的将亡值，则decltype(e)是T&&。
// 5．除去以上情况，则decltype(e)是T

const int&& foo();
int i;
struct A {
    double x;
};
const A* a = new A();
decltype(foo());         // decltype(foo())推导类型为const int&&
decltype(i);             // decltype(i)推导类型为int
decltype(a->x);          // decltype(a->x)推导类型为double
decltype((a->x));        // decltype((a->x))推导类型为const double&

// decltype(e)所推导的类型会同步e的cv限定符.
// auto 会忽略CV限定符.

// 在C++17标准中decltype(auto)也能作为非类型
// 模板形参的占位符，其推导规则和上面介绍的保持一致
// 也就是按照decltype的推到方式, 变成T类型.
template<decltype(auto) N>
void f()
{
    std::cout << N << std::endl;
}

static const int x = 11;
static int y = 7;

int main()
{
    f<x>();		// N为const int类型
    f<(x)>();	// N为const int&类型
    // f<y>();		// 编译错误
    f<(y)>();	// N为int&类型
}
